// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: jhkim, junglekim

#include "coin2/strategy/cmm_strat/cmm_order_controller.h"

#include <algorithm>
#include <cstdlib>
#include <random>

#include <glog/logging.h>
#include <time.h>

namespace coin2::strategy::cmm_strat {

CmmOrderController::CmmOrderController(
    const ::nlohmann::json& oc_config,
    std::optional<int64_t> ts,
    bool dry_run)
    : oc_config_(oc_config),
      strategy_created_time_(ts.has_value() ? *ts : GetCurrentTimestamp()),
      dry_run_(dry_run),
      accept_wait_time_ms_(oc_config_["accept_wait_time_ms"].get<double>()),
      is_same_owner_(oc_config_["owner"]["exec0"] == oc_config_["owner"]["exec1"]) {}

void CmmOrderController::InitExecutor(
    PassiveExecutorConfig exe_config0,
    PassiveExecutorConfig exe_config1,
    IOrderSubsystem* os0,
    IOrderSubsystem* os1,
    Driver* driver) {
  driver_ = driver;
  auto get_product_holder =
      [this](const coin2::exchange::base::symbology::IProduct& product, int64_t timestamp) {
        return driver_->product_holder_manager()->GetProductHolder(product, timestamp);
      };
  executor0_ = std::make_shared<PassiveOrderExecutor>(
      exe_config0,
      os0,
      strategy_created_time_,
      nullptr,
      get_product_holder);
  SPDLOG_INFO("PASSIVE_EXECUTOR_CONFIG0: {}", exe_config0.DebugString());
  if (is_same_owner_) {
    executor1_ = executor0_;
  } else {
    executor1_ = std::make_unique<PassiveOrderExecutor>(
        exe_config1,
        os1,
        strategy_created_time_,
        nullptr,
        get_product_holder);
    SPDLOG_INFO("PASSIVE_EXECUTOR_CONFIG1: {}", exe_config1.DebugString());
  }
}

void CmmOrderController::SubmitAggOrder(int64_t ts, double price, double qty, OrderSide side) {
  if (side == OrderSide::BUY_ORDER) {
    executor1_->ForceFireBuyOrder(ts, price, qty, "aggressive");
  } else {
    executor1_->ForceFireSellOrder(ts, price, qty, "aggressive");
  }
}

int CmmOrderController::TossRandomSide(void) {
  srand(time(0));
  int side = 1 + rand() % 2;

  return side;
}

int CmmOrderController::GenSide(double avg_period_min, double qty_1month, double imb_threshold) {
  SPDLOG_INFO("{} {} CHECK", executor0_->GetPosition(), executor1_->GetPosition());
  if (is_same_owner_) {
    return TossRandomSide();
  }

  double diff = executor0_->GetPosition() - executor1_->GetPosition();
  double avg_qty = avg_period_min * qty_1month / 43200;
  if (diff > avg_qty * imb_threshold) {
    return 2;
  } else if (diff < -avg_qty * imb_threshold) {
    return 1;
  } else {
    return TossRandomSide();
  }
}

double CmmOrderController::GenRandomQty(
    double avg_period_min,
    double qty_1month,
    double range,
    const IProductInfo* pi_) {
  double avg_qty = avg_period_min * qty_1month / 43200;
  std::uniform_real_distribution<double> distribution(avg_qty * (1 - range), avg_qty * (1 + range));
  std::random_device rd;
  std::default_random_engine generator(rd());
  double qty = pi_->tick_qty().Round(distribution(generator));

  return qty;
}

void CmmOrderController::onCleanup0() {
  executor0_->ManageOrders(GetCurrentTimestamp());
  executor0_->ForceCancelAllWorkingOrders();
}

void CmmOrderController::onCleanup1() {
  executor1_->ManageOrders(GetCurrentTimestamp());
  executor1_->ForceCancelAllWorkingOrders();
}

void CmmOrderController::onFairPriceUpdate(
    double bid,
    double ask,
    int64_t timestamp_,
    const IProductInfo* pi_,
    const IProduct* product,
    Driver* driver) {
  if (!executor0_ || !executor1_) {
    return;
  }
  if (!driver_->order()->GetUniqueSystemByName("acct0") ||
      !driver_->order()->GetUniqueSystemByName("acct0")->is_ready() ||
      !driver_->order()->GetUniqueSystemByName("acct1") ||
      !driver_->order()->GetUniqueSystemByName("acct1")->is_ready()) {
    return;
  }
  int side =
      GenSide(oc_config_["avg_period_min"], oc_config_["qty_1month"], oc_config_["imb_threshold"]);
  if (ask == 0 && bid == 0) {
    SPDLOG_WARN("Condition not satisfied.");
    return;
  } else if (ask == 0) {
    side = 1;
  } else if (bid == 0) {
    side = 2;
  }
  OrderSide order_side;
  double order_price;
  double qty = GenRandomQty(
      oc_config_["avg_period_min"],
      oc_config_["qty_1month"],
      oc_config_["qty_range"],
      pi_);
  if (side == 1) {
    order_side = OrderSide::BUY_ORDER;
    order_price = bid;
  } else {
    order_side = OrderSide::SELL_ORDER;
    order_price = ask;
  }
  if (!dry_run_) {
    OrderSpec order_spec(
        *product,
        OrderType::LIMIT_ORDER,
        order_side,
        OrderDuration::GTC_ORDER,
        order_price,
        qty,
        timestamp_,
        true,
        0,
        "passive");
    driver->order()->GetUniqueSystemByName("acct0")->gateway()->SubmitOrder(order_spec);
    is_in_time_limit_ = true;
    is_agg_triggered_ = false;
    boost::posix_time::nanoseconds interval(
        static_cast<int64_t>(accept_wait_time_ms_ * 1'000'000L));
    state_timer_ =
        std::make_unique<boost::asio::deadline_timer>(*driver->live_executor()->RawIoc(), interval);
    state_timer_->expires_from_now(interval);
    state_timer_->async_wait(std::bind(&CmmOrderController::ControlState, this));
  }
}

void CmmOrderController::ControlState() {
  // if no agg order within 100ms -> no agg order even passive order accepted after 100ms
  if (!is_agg_triggered_) {
    is_in_time_limit_ = false;
  }
  // agg order within 100ms -> Nothing to do
}

void CmmOrderController::onAccountInfo(const OrderUpdate& upd) {
  auto& account_info = upd.account_info();
  if (account_info.owner() == oc_config_["owner"]["exec0"]) {
    executor0_->onAccountInfo(upd);
    if (is_same_owner_) return;
    if (is_pos0_ready_) {
      return;
    }
    is_pos0_ready_ = true;
  } else {
    executor1_->onAccountInfo(upd);
    if (is_same_owner_) return;
    if (is_pos1_ready_) {
      return;
    }
    is_pos1_ready_ = true;
  }
}

void CmmOrderController::onAccountOrder(const OrderUpdate& upd) {
  auto oe = upd.product_order_info().event();

  if (oe.tag() == "aggressive") {
    executor1_->onAccountOrder(upd);
    is_agg_triggered_ = true;
    if (oe.type() == coin::proto::OrderEvent::ORDER_ACCEPTED) {
      onCleanup1();
    } else if (oe.type() == coin::proto::OrderEvent::ORDER_REJECTED) {
      onCleanup0();
    }
  } else if (oe.tag() == "passive") {
    executor0_->onAccountOrder(upd);
    // Passive Accepted within 100ms -> Submit aggression order
    if (oe.type() == coin::proto::OrderEvent::ORDER_ACCEPTED && is_in_time_limit_) {
      if (oe.trade_side() == coin::proto::TRADE_BUY_SIDE) {
        SubmitAggOrder(
            upd.timestamp(),
            oe.order_price(),
            oe.order_qty(),
            OrderSide::SELL_ORDER);
      } else {
        SubmitAggOrder(
            upd.timestamp(),
            oe.order_price(),
            oe.order_qty(),
            OrderSide::BUY_ORDER);
      }
      is_agg_triggered_ = true;
      // Passive Accepted not within 100ms -> Cleanup Every Order
    } else if (oe.type() == coin::proto::OrderEvent::ORDER_ACCEPTED && !is_in_time_limit_) {
      onCleanup0();
    }
  }
  if (oe.type() == coin::proto::OrderEvent::ORDER_FILLED) {
    if (oe.tag() == "aggressive") {
      onCleanup1();
    } else if (oe.tag() == "passive") {
      onCleanup0();
    }
  }
}
}  // namespace coin2::strategy::cmm_strat